package com.mall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.mall.common.constant.OrderConstant;
import com.mall.common.dto.*;
import com.mall.common.page.PageData;
import com.mall.common.service.impl.CrudServiceImpl;
import com.mall.common.utils.ConvertUtils;
import com.mall.common.utils.Result;
import com.mall.common.vo.UserInfoVo;
import com.mall.order.dao.OrderDao;
import com.mall.order.dto.OrderDTO;
import com.mall.order.dto.OrderItemDTO;
import com.mall.order.dto.WareSkuLockDto;
import com.mall.order.entity.OrderEntity;
import com.mall.order.entity.OrderItemEntity;
import com.mall.order.enume.OrderStatusEnum;
import com.mall.order.feign.CartFeignClient;
import com.mall.order.feign.MemberFeignClient;
import com.mall.order.feign.ProductFeignClient;
import com.mall.order.feign.WareFeignClient;
import com.mall.order.interceptor.OrderInterceptor;
import com.mall.order.service.OrderItemService;
import com.mall.order.service.OrderService;
import com.mall.order.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 订单
 *
 * @author xjc xjc@163.com
 * @since 1.0.0 2022-07-17
 */
@Service
@Slf4j
public class OrderServiceImpl extends CrudServiceImpl<OrderDao, OrderEntity, OrderDTO> implements OrderService {

    @Autowired
    private MemberFeignClient memberFeignClient;
    @Autowired
    private CartFeignClient cartFeignClient;
    @Autowired
    private WareFeignClient wareFeignClient;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private ProductFeignClient productFeignClient;
    @Resource
    private OrderItemService orderItemService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public QueryWrapper<OrderEntity> getWrapper(Map<String, Object> params){
        String id = (String)params.get("id");
        QueryWrapper<OrderEntity> wrapper = new QueryWrapper<>();
        wrapper.eq(StringUtils.isNotBlank(id), "id", id);
        return wrapper;
    }

    // todo
    @Override
    public OrderConformVo orderConform() {
        UserInfoVo userInfoVo = OrderInterceptor.threadLocal.get();
        OrderConformVo orderConformVo = new OrderConformVo();
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        // 用线程池异步调用会 获取不到 session
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            Result<List<MemberReceiveAddressDTO>> allReceiveAddress = memberFeignClient.getAllReceiveAddress(userInfoVo.getId());
            List<MemberReceiveAddressDTO> data = allReceiveAddress.getData();
            orderConformVo.setMemberAddress(data);
        },threadPoolExecutor);
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            Result<List<OrderItemVo>> result = cartFeignClient.getCurrentCartItems();
            orderConformVo.setItems(result.getData());
        },threadPoolExecutor).thenRunAsync(()->{
            RequestContextHolder.setRequestAttributes(attributes);
            List<OrderItemVo> items = orderConformVo.getItems();
            List<Long> skuIds = items.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
            Result<List<Long>> result = wareFeignClient.hasStock(skuIds);
            Set<Long> stock = new HashSet<>(result.getData());
            List<OrderItemVo> items_stock = items.stream().map(item -> {
                item.setHasStock(stock.contains(item.getSkuId()));
                return item;
            }).collect(Collectors.toList());
            orderConformVo.setItems(items_stock);
        });
        CompletableFuture<Void> couponFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            // todo 查询所有的优惠信息
            List<CouponVO> couponVOS = new ArrayList<>();
            couponVOS.add(new CouponVO());
            orderConformVo.setCoupons(couponVOS);
        },threadPoolExecutor);
        // todo 其他折扣数据
        // todo 发放订单令牌
        String key = OrderConstant.ORDER_TOKEN_PREFIX + userInfoVo.getId();
        String order_token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(key,order_token,15, TimeUnit.MINUTES);
        orderConformVo.setOrderToken(order_token);
        try {
            CompletableFuture.allOf(addressFuture,cartFuture,couponFuture).get();
            return orderConformVo;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("订单确认远程调用异常："+e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    // todo 保证最终一致性
    @Transactional
    @Override
    public OrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        UserInfoVo userInfoVo = OrderInterceptor.threadLocal.get();
        // 令牌验证
        String key = OrderConstant.ORDER_TOKEN_PREFIX + userInfoVo.getId().toString();
        String orderToken = orderSubmitVo.getOrderToken();
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end ";
        Long execute = (Long) redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(key), orderToken);
        if (execute!=1){
            return new OrderResponseVo(null,400);
        }
        // 创建订单
        OrderDTO order = createOrder(orderSubmitVo);
        order.setMemberId(userInfoVo.getId());
        order.setMemberUsername(userInfoVo.getUsername());
        order.setCreateTime(new Date());
        // 计算价格
        order = computePrice(order);
        // 验价
        log.info("后台计算价格：{}",order.getPayAmount() );
        log.info("前端计算价格：{}",orderSubmitVo.getPayAmount() );
        // 保存订单项
        saveOrder(order);
        OrderDTO finalOrder = order;
//        CompletableFuture<Void> wareFuture = CompletableFuture.runAsync(() -> {
            // 锁库存
            WareSkuLockDto lockDto = new WareSkuLockDto();
            lockDto.setOrderSn(finalOrder.getOrderSn());
            lockDto.setLocks(ConvertUtils.sourceToTarget(finalOrder.getItems(), OrderLockItemDTO.class));
            Result result = wareFeignClient.orderLockStock(lockDto);
            log.info(result.toString());
            if (result.getCode()!=0)
                throw new RuntimeException("库存不足");
//        }, threadPoolExecutor);
//        CompletableFuture<Void> memberFuture = CompletableFuture.runAsync(() -> {
            // 扣积分
//        }, threadPoolExecutor);
        try {
//            CompletableFuture.allOf(wareFuture,memberFuture).get();
            // 成功
            // 删除购物车
            //
            OrderNoteDto noteDto = new OrderNoteDto(order.getOrderSn());
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            ReturnedMessage returnedMessage = new ReturnedMessage(new Message(JSON.toJSONBytes(order)),200,"com.mall.order.dto.OrderDTO","order-event-exchange","order.create");
            correlationData.setReturned(returnedMessage);
            rabbitTemplate.convertAndSend("order-event-exchange","order.create",noteDto,correlationData);
            // 订单放入缓存中
            redisTemplate.opsForValue().set(
                    OrderConstant.ORDER_PAY_PREFIX + order.getOrderSn(),
                    JSON.toJSONString(order),
                    OrderConstant.ORDER_REDIS_TIMEOUT_LENGTH,
                    OrderConstant.ORDER_REDIS_TIMEOUT_TYPE);
            return new OrderResponseVo(order,0);
        } catch (Exception e) {
            e.printStackTrace();
            // 库存回滚
//            OrderNoteDto noteDto = new OrderNoteDto(order.getOrderSn());
//            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//            ReturnedMessage returnedMessage = new ReturnedMessage(new Message(JSON.toJSONBytes(order)),200,"com.mall.order.dto.OrderDTO","order-event-exchange","order.release.other");
//            correlationData.setReturned(returnedMessage);
//            rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",noteDto,correlationData);
            // 订单放入缓存中
            // 积分回滚
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void saveOrder(OrderDTO order) {
        List<OrderItemDTO> items = order.getItems();
        this.save(order);
        orderItemService.insertBatch(ConvertUtils.sourceToTarget(items, OrderItemEntity.class));
    }

    @Override
    public OrderDTO getByOrderSn(String orderSn) {
        QueryWrapper<OrderEntity> queryWrapper = new QueryWrapper<OrderEntity>().eq("order_sn", orderSn);
        OrderEntity orderEntity = baseDao.selectOne(queryWrapper);
        return ConvertUtils.sourceToTarget(orderEntity,OrderDTO.class);
    }

    @Override
    public void closeOrder(String orderSn) {
        QueryWrapper<OrderEntity> queryWrapper = new QueryWrapper<OrderEntity>().eq("order_sn", orderSn);
        queryWrapper.eq("status",OrderConstant.StatusEnum.WAIT_FOR_PAY.getType());
        OrderEntity order = baseDao.selectOne(queryWrapper);
        if (order!=null&& Objects.equals(order.getStatus(), OrderStatusEnum.CREATE_NEW.getCode())){
            OrderEntity orderEntity = new OrderEntity();
            orderEntity.setId(order.getId());
            orderEntity.setStatus(OrderStatusEnum.CANCLED.getCode());
            baseDao.updateById(orderEntity);
        }
//        // todo 发送解锁库存消息
//        OrderNoteDto releaseDto = new OrderNoteDto(orderSn);
//        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//        ReturnedMessage returnedMessage = new ReturnedMessage(
//                new Message(
//                        JSON.toJSONBytes(releaseDto)),
//                200,
//                "com.mall.common.dto.OrderNoteDto",
//                "order-event-exchange",
//                "order.release.other");
//        correlationData.setReturned(returnedMessage);
//        rabbitTemplate.convertAndSend("order-event-exchange","order.release.other",releaseDto);
    }

    @Override
    public PageData<OrderVo> getPage(Map<String, Object> params) {
        UserInfoVo userInfoVo = OrderInterceptor.threadLocal.get();
        Long userId = userInfoVo.getId();
        List<OrderEntity> orders = baseDao.listOrderSn(userId);
        List<String> orderSns = orders.stream().map(OrderEntity::getOrderSn).collect(Collectors.toList());
        List<OrderItemDTO> data = orderItemService.getPage(orderSns);
        List<OrderVo> list = orders.stream().map(item -> {
            OrderVo orderVo = new OrderVo();
            BeanUtils.copyProperties(item, orderVo);
            List<OrderItemVo> items = data.stream()
                    .filter(i -> i.getOrderSn().equals(item.getOrderSn()))
                    .map(detail ->{
                        OrderItemVo orderItemVo = new OrderItemVo();
                        orderItemVo.setImg(detail.getSkuPic());
                        orderItemVo.setTitle(detail.getSkuName());
                        String[] split = detail.getSkuAttrsVals().split(";");
                        orderItemVo.setSkuAttr(Arrays.asList(split));
                        orderItemVo.setCount(detail.getSkuQuantity());
                        return orderItemVo;
                    })
                    .collect(Collectors.toList());
            orderVo.setItems(items);
            return orderVo;
        }).filter(order->order.getItems().size()>0).collect(Collectors.toList());
        return new PageData<>(list,list.size());
    }

    @Override
    public void successOrder(OrderNoteDto order) {
        UpdateWrapper<OrderEntity> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("status",OrderConstant.StatusEnum.WAIT_FOR_CONSIGNMENT.getType());
        updateWrapper.eq("order_sn",order.getOrderSn());
        baseDao.update(null,updateWrapper);
    }

    @Override
    @Transactional
    public void saveSeckillOrder(SeckillOrderDTO seckillOrderDTO) throws Exception{
        // todo
        OrderDTO order = new OrderDTO();
        order.setOrderSn(seckillOrderDTO.getOrderSn());
        order.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        order.setMemberId(seckillOrderDTO.getMemberId());
        order.setMemberUsername(seckillOrderDTO.getMemberUserName());
        order.setCreateTime(new Date());
        BigDecimal multiply = seckillOrderDTO.getSeckillPrice().multiply(seckillOrderDTO.getSeckillCount());
        order.setPayAmount(multiply);
        order.setTotalAmount(multiply);
        order.setFreightAmount(new BigDecimal(0));
        save(order);
        //
        OrderItemEntity orderItem = new OrderItemEntity();
        orderItem.setOrderSn(order.getOrderSn());
        orderItem.setRealAmount(multiply);
        orderItem.setSkuQuantity(seckillOrderDTO.getSeckillCount().intValue());
//        SpuInfoDTO spuInfoDTO = spuInfo.get();
//        orderItem.setSpuPic();
        orderItemService.insert(orderItem);

    }


    private OrderDTO computePrice(OrderDTO order) {
        // 订单总额
        BigDecimal totalAmount = new BigDecimal("0");
        BigDecimal promotionAmount = new BigDecimal("0");
        BigDecimal couponAmount = new BigDecimal("0");
        BigDecimal integrationAmount = new BigDecimal("0");
        Integer giftIntegration = 0;
        Integer giftGrowth = 0;
        List<OrderItemDTO> items = order.getItems();
        for (OrderItemDTO item : items) {
            totalAmount = totalAmount.add(item.getRealAmount());
            promotionAmount = promotionAmount.add(item.getPromotionAmount());
            couponAmount = couponAmount.add(item.getCouponAmount());
            integrationAmount = integrationAmount.add(item.getIntegrationAmount());
            giftIntegration += item.getGiftIntegration();
            giftGrowth += item.getGiftGrowth();
        }
        order.setTotalAmount(totalAmount);
        order.setPromotionAmount(promotionAmount);
        order.setCouponAmount(couponAmount);
        order.setIntegrationAmount(integrationAmount);
        // 应付总额
        order.setPayAmount(totalAmount.add(order.getFreightAmount()));
        // 积分成长值
        order.setGrowth(giftGrowth);
        order.setIntegration(giftIntegration);

        return order;
    }

    private OrderDTO createOrder(OrderSubmitVo orderSubmitVo) throws RuntimeException{
        UserInfoVo userInfoVo = OrderInterceptor.threadLocal.get();
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        OrderDTO orderDTO = new OrderDTO();
        // todo 订单编号
        orderDTO.setOrderSn(IdWorker.getTimeId());
        orderDTO.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        orderDTO.setDeleteStatus(0);
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            MemberReceiveAddressDTO data = memberFeignClient.get(orderSubmitVo.getAddrId()).getData();
            orderDTO.setReceiverDetailAddress(data.getDetailAddress());
            // todo 计算运费
            orderDTO.setFreightAmount(new BigDecimal("8"));
            orderDTO.setReceiverCity(data.getCity());
            orderDTO.setReceiverName(data.getName());
            orderDTO.setReceiverProvince(data.getProvince());
            orderDTO.setReceiverPhone(data.getPhone());
            orderDTO.setReceiverPostCode(data.getPostCode());
            orderDTO.setReceiverRegion(data.getRegion());
        }, threadPoolExecutor);
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            // 最后一次计算价格
            Result<List<OrderItemVo>> result = cartFeignClient.getCurrentCartItems();
            List<OrderItemVo> data = result.getData();
            List<OrderItemDTO> items = data.stream()
                    .filter(item->item.getCount()>0)
                    .map(item -> {
                        OrderItemDTO orderItemDTO = new OrderItemDTO();
                        orderItemDTO.setOrderSn(orderDTO.getOrderSn());
                        orderItemDTO.setSkuId(item.getSkuId());
                        orderItemDTO.setSkuName(item.getTitle());
                        orderItemDTO.setSkuPic(item.getImg());
                        orderItemDTO.setSkuPrice(item.getPrice());
                        orderItemDTO.setSkuQuantity(item.getCount());
                        String attr = org.springframework.util.StringUtils.collectionToDelimitedString(item.getSkuAttr(), ";");
                        orderItemDTO.setSkuAttrsVals(attr);
                        // 优惠不做
                        orderItemDTO.setGiftGrowth(100*item.getCount());
                        orderItemDTO.setGiftIntegration(100*item.getCount());
                        // spu
                        SpuInfoDTO spuInfo = productFeignClient.getSpuInfoBySkuId(item.getSkuId()).getData();
                        orderItemDTO.setSpuId(spuInfo.getId());
                        orderItemDTO.setSpuName(spuInfo.getSpuName());
                        orderItemDTO.setSpuPic(spuInfo.getSpuDescription());
                        // catelog
                        orderItemDTO.setCategoryId(spuInfo.getCatelogId());
                        // brandName 应该冗余存储
                        orderItemDTO.setSpuBrand(spuInfo.getBrandId().toString());
                        //
                        orderItemDTO.setPromotionAmount(new BigDecimal("0.0"));
                        orderItemDTO.setCouponAmount(new BigDecimal("0.0"));
                        orderItemDTO.setIntegrationAmount(new BigDecimal("0.0"));
                        BigDecimal multiply = orderItemDTO.getSkuPrice().multiply(new BigDecimal(orderItemDTO.getSkuQuantity().toString()));
                        BigDecimal subtract = multiply.subtract(orderItemDTO.getPromotionAmount())
                                .subtract(orderItemDTO.getCouponAmount())
                                .subtract(orderItemDTO.getIntegrationAmount());
                        orderItemDTO.setRealAmount(subtract);

                        return orderItemDTO;
                    }).collect(Collectors.toList());
            orderDTO.setItems(items);
        },threadPoolExecutor);
        try {
            CompletableFuture.allOf(addressFuture,cartFuture).get();
            return orderDTO;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

}